import json
import os
import struct
import argparse
from typing import BinaryIO, Tuple, List


class Codes:
  def __init__(self, items: 'Items'):
    self.codes = {}
    for item in items.items:
      self.codes.setdefault(item.code, []).append(item)
    for code, items in self.codes.items():
      self.codes[code] = sorted(items, key=lambda it: it.date)

  def save(self, dir_name, white_codes=None):
    for code, items in self.codes.items():
      if white_codes is not None and code not in white_codes:
        continue
      data = [item.to_dict() for item in items]
      with open(f'{dir_name}/{code}.json', 'w', encoding='utf8') as f:
        f.write(json.dumps(data, indent=2, ensure_ascii=False))


class Item:
  def __init__(self, index, data):
    self.index = index
    self.data = data
    self.date = struct.unpack('<i', data[0:4])[0]
    self.unknown_04 = struct.unpack('<i', data[4:8])[0]
    self.code = data[9:15].decode('ascii')
    self.launch_price = struct.unpack('<d', data[0x36:0x3e])[0]  # 实际发行价
    self.unknown_3e = struct.unpack('<d', data[0x3e:0x46])[0]
    self.launch_count = struct.unpack('<d', data[0x4e:0x56])[0]  # 实际发行量（股）
    self.unknown_d8 = struct.unpack('<f', data[0xd8:0xdc])[0]
    self.unknown_f8 = struct.unpack('<f', data[0xf8:0xfc])[0]
    self.unknown_100 = struct.unpack('<f', data[0x100:0x104])[0]
    self.unknown_110 = struct.unpack('<f', data[0x110:0x114])[0]
    self.notice_date = struct.unpack('<i', data[0x5e:0x62])[0]  # IPO发行日（招股公告日）
    self.launch_date = struct.unpack('<i', data[0x62:0x66])[0]  # IPO上市日

  def to_dict(self):
    return {
      'date': self.date,
      'notice_date': self.notice_date,
      'launch_date': self.launch_date,
      'launch_count': self.launch_count,
      'launch_price': self.launch_price,
    }

  @staticmethod
  def hexdump(data: bytes, width: int = 16):
    """
    打印 bytes 对象，带偏移地址和列标题，每行显示 width 个字节。

    :param data: 要打印的 bytes 对象
    :param width: 每行显示的字节数，默认为 16
    """
    # 打印列标题
    print()
    header = "OFFSET    " + " ".join(f"{i:02x}" for i in range(width))
    separator = "----------" + "-" * (width * 3)
    print(header)
    print(separator)

    # 打印数据
    for i in range(0, len(data), width):
      # 当前行的偏移地址（十六进制）
      offset = f"{i:08x}"

      # 当前行的字节（十六进制表示）
      hex_part = ' '.join(f"{b:02x}" for b in data[i:i + width])

      # 补齐空格，使对齐美观（当不足 width 字节时）
      hex_part = hex_part.ljust(width * 3)

      # 可打印字符部分（不可打印的用 '.' 替代）
      ascii_part = ''.join(chr(b) if 32 <= b <= 126 else '.' for b in data[i:i + width])

      print(f"{offset}  {hex_part}  |{ascii_part}|")
    print()

  def __repr__(self):
    self.hexdump(self.data)
    return f'<Item {self.index} code={self.code} date={self.date} issue_price={self.launch_price} issue_count={self.launch_count} notice_date={self.notice_date} launch_date={self.launch_date} unknown: 3e={self.unknown_3e} d8={self.unknown_d8} f8={self.unknown_f8} 100={self.unknown_100} 120={self.unknown_110}>'


class Items:
  def __init__(self, f: BinaryIO):
    self.items = []
    index = 0
    data = f.read(0x12c)
    while data != b'':
      item = Item(index, data)
      if item.code.isdigit():
        self.items.append(item)
        if item.code == '301668':
          print('item=', item.code, self.items[-1])
      data = f.read(0x12c)
      index += 1
    print('end tell=', f.tell())

  def __repr__(self):
    return f'<Items count={len(self.items)} firstItem={self.items[0]} lastItem={self.items[-1]}>'


class Head:
  """
  文件头信息类，存储数据文件的基本信息
  """

  def __init__(self, data: bytes):
    print(f'data={data.hex(" ")}')
    self.date = struct.unpack('<i', data[:4])[0]  # 文件日期
    self.unknown_04 = struct.unpack('<i', data[4:8])[0]

  def __repr__(self):
    return f'<Head date={self.date} unknon: 04={self.unknown_04}>'


class Reader:
  """
  数据文件读取器主类，负责解析整个数据文件
  """

  def __init__(self, file_path):
    self.head = None  # type: Head | None       # 文件头信息
    self.items = None  # type: Items | None
    self.codes = None  # type: Codes | None

    # 以二进制模式打开文件并开始解析
    with open(file_path, 'rb') as f:
      self.f = f
      self._analyze()

  def _analyze(self):
    """
    分析整个数据文件，依次解析头部、代码和数据块信息
    """
    self.head = self._analyze_head()  # 解析文件头
    print('self.head=', self.head)
    self.items = self._analyze_items()
    print('self.items=', self.items)
    self.codes = self._analyze_codes()
    print('self.codes=', self.codes)

  def _analyze_head(self, length=0x8):
    return Head(self.f.read(length))

  def _analyze_items(self):
    return Items(self.f)

  def _analyze_codes(self):
    return Codes(self.items)

  def save(self, save_path, white_codes=None):
    """
    保存解析后的数据为JSON文件
    :param save_path: 保存目录路径
    :param white_codes: 白名单股票代码列表，如果指定则只保存白名单内的股票数据
    """
    os.makedirs(save_path, exist_ok=True)  # 创建保存目录（如果不存在）
    self.codes.save(save_path, white_codes)


def main(file_path, save_path, save_white_path=None):
  """
  主函数，解析本地数据文件并保存为JSON格式
  :param file_path: 数据文件路径
  :param save_path: JSON数据保存路径
  :param save_white_path: 白名单文件路径（可选）
  """
  white_codes = None
  # 如果指定了白名单文件，则读取白名单股票代码
  if save_white_path is not None:
    with open(save_white_path, encoding='utf8') as f:
      white_codes = f.read().split('\n')

  # 创建读取器并解析数据文件
  reader = Reader(file_path)
  # 保存解析后的数据
  reader.save(save_path, white_codes)


if __name__ == '__main__':
  """
  程序入口点，支持命令行参数
  """
  parser = argparse.ArgumentParser(description='IPO数据V4解析器')
  parser.add_argument('input_file', help='IPO数据V4数据文件路径')
  parser.add_argument('output_dir', help='输出目录路径')
  parser.add_argument('--whitelist', '-w', help='白名单文件路径（可选）')

  args = parser.parse_args()

  main(args.input_file, args.output_dir, args.whitelist)
